package com.atguigu.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.gulimall.product.service.CategoryBrandRelationService;
import com.atguigu.gulimall.product.vo.Catelog2Vo;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import org.w3c.dom.ls.LSException;

import javax.rmi.CORBA.Util;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

     private Map<String,Object> cache = new HashMap<>();

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    RedisTemplate<String, String> redisTemplate;

    @Autowired
    RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        //1、查出所有的一级分类
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);
        //2、组装父子的树形结构
        //2.1找到所有的一级分类
        List<CategoryEntity> collect = categoryEntities.stream().filter(categoryEntity -> {
            //先找到父菜单
            return  categoryEntity.getParentCid()!=null&&categoryEntity.getParentCid() == 0;
        }).map(categoryEntity->{
            //找到子菜单
            categoryEntity.setChildren(getChildren(categoryEntity,categoryEntities));
            return categoryEntity;
        }).sorted((menu1,menu2)->{
            //菜单的排序
            return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
        }).collect(Collectors.toList());

        return collect;
    }

    @Override
    public Void deletedMenuByIds(Collection<?> list) {
        //TODO 1.检查当前删除的菜单，是否在别的地方引用
        baseMapper.deleteBatchIds(list);

        return null;
    }

    /**
     * 获取 分类的三级路径
     * @param catelogId
     * @return
     */
    @Override
    public Long[] getCategoryPath(Long catelogId) {
        List<Long> longs = new ArrayList<>();
        List<Long> path = getParentById(catelogId, longs);
        Collections.reverse(path);

        return path.toArray(new Long[path.size()]);
    }

    /**
     * 修改分类
     * @CacheEvict:失效模式 其中key value的值比较跟读的一致
     * 1、同时操作多种缓存失效操作 @Caching 注解
     * 2、指定某个分区下的所有数据  @CacheEvict(value = {"catetory"},allEntries=true)
     * 3、默认最好不要设定缓存的前缀 ，spring会以value作为前缀catetory:
     * @param category
     */
//    @Caching(evict = {
//           @CacheEvict(value = {"catetory"},key = "'getLevel1Categorys'"),
//           @CacheEvict(value = {"catetory"},key = "'getLevel1Categorys'"),
//    })
    @CacheEvict(value = {"catetory"},allEntries = true)
    @Transactional //开启事务
    @Override
    public void updateDetail(CategoryEntity category) {
        categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());

        this.updateById(category);
    }

    @Override
    //每一个需要缓存的数据我们都来指定要放名字的缓存
    @Cacheable(value = {"catetory"},key = "#root.methodName")
    //会将方法的结果缓存到指定的key中的 key默认自动生成，缓存名字::simplekey[] 值默认是jdk默认的序列化值 默认过期时间-1永不过期
    //其中key是指定缓存的key 接收Spel
    //可以指定数据的存活ttl 配置文件中修改ttl
    //将数据保存为json格式
    public List<CategoryEntity> getLevel1Categorys() {
        System.out.println("getLevel1Categorys....");
        long l = System.currentTimeMillis();
        List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
        System.out.println("消耗时间，" + (System.currentTimeMillis() - 1));
        return categoryEntities;
    }

    /**
     * TODO 产生堆外内存泄露： io.netty.util.internal.OutOfDirectMemoryError
     * 1）springboot2.0 默认使用lettuce 作为操作redis的客户端，它使用netty进行网络通讯
     * 2）lettuce的bug导致netty堆外内存溢出，-Xmx300m netty如果没有制定堆外内存，默认使用-Xmx300m
     * 解决：升级lettuce / 使用jedis  jedis很久没更新了 lettuce并发比较好
     *
     * <!--整合redis-->
     * 		<dependency>
     * 			<groupId>org.springframework.boot</groupId>
     * 			<artifactId>spring-boot-starter-data-redis</artifactId>
     * 			<exclusions>
     * 				<!-- 为了解决lettuce出现的堆外内存溢出 -->
     * 				<exclusion>
     * 					<groupId>io.lettuce</groupId>
     * 					<artifactId>lettuce-core</artifactId>
     * 				</exclusion>
     * 			</exclusions>
     * 		</dependency>
     *
     *
     * @return
     */
    @Override
    public Map<String, List<Catelog2Vo>> cataLogJson() {
        //给缓存放json字符串，拿出json字符串，还用逆转为能用的对象类型【序列化/反序列化】
        //1加入缓存逻辑，缓存中存的数据是json字符串
        //json的扩平台
        /**
         * 空结果缓存：缓存穿透
         * 设置随机过期时间：缓存雪崩
         * 加锁：缓存击穿
         *
         */
        Map<String, List<Catelog2Vo>> stringListMap = null;
        ValueOperations vps = redisTemplate.opsForValue();
        String category_log = (String) vps.get("categorys_log");
        if(StringUtils.isEmpty(category_log)){
            System.out.println("redis 没有缓存");
            stringListMap = cataLogJsonFromDbWithRedisLock();
        }else{
            System.out.println("redis 命中缓存");
            stringListMap = JSON.parseObject(category_log,new TypeReference<Map<String, List<Catelog2Vo>>>(){});//TypeReference 受保护的必须加{} new TypeReference为了转类型
        }

        return stringListMap;
    }

    /**
     * 获取redis数据
     *
     * @return
     */
    public Map<String, List<Catelog2Vo>> cataLogJsonFromDbWithRedissonLock() {
        String category_log = (String) redisTemplate.opsForValue().get("categorys_log");
        if(!StringUtils.isEmpty(category_log)){//这里加上判断是为了不要频繁抢占锁 提高响应的效率
            System.out.println("自旋获取到的数据");
            return JSON.parseObject(category_log,new TypeReference<Map<String, List<Catelog2Vo>>>(){});//TypeReference 受保护的必须加{} new TypeReference为了转类型
        }
        RLock lock = redissonClient.getLock("catelogjson-lock");
        lock.lock();//加锁
        System.out.println("抢占到锁");
        Map<String, List<Catelog2Vo>> stringListMap=null;
        try{
            stringListMap = cateLogJsonFromDb();
        }finally {
            lock.unlock();
        }

        return stringListMap;
    }

    /**
     * 获取redis数据
     *
     * @return
     */
    @Cacheable(value = {"catetory"},key = "#root.methodName")
    public Map<String, List<Catelog2Vo>> cataLogJsonFromDbWithRedisLock() {
        //1、占分布式锁，去redis站坑
        String uuid = UUID.randomUUID().toString();//lock是key 也就是删除key lock只允许一个进程
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);//设置缓存跟过期时间必须是原子操作
        String category_log = (String) redisTemplate.opsForValue().get("categorys_log");
        if(!StringUtils.isEmpty(category_log)){//这里加上判断是为了不要频繁抢占锁 提高
            System.out.println("自旋获取到的数据");
            return JSON.parseObject(category_log,new TypeReference<Map<String, List<Catelog2Vo>>>(){});//TypeReference 受保护的必须加{} new TypeReference为了转类型
        }
        if(aBoolean){
            System.out.println("抢占到锁");
            Map<String, List<Catelog2Vo>> stringListMap=null;
                //此处断电了
//            redisTemplate.expire("lock",30,TimeUnit.SECONDS); //上处断电了，lock就加不上过期时间：设置过期时间必须和加锁是一致的 原子性的

//            System.out.println("抢占到锁");
//            Map<String, List<Catelog2Vo>> stringListMap = cateLogJsonFromDb();
//            if(uuid.equals(redisTemplate.opsForValue().get("lock"))){//这里会因为redis的传输时差问题，在对比uuid那刻，lock就已经过期了，导致流量进来
//                redisTemplate.delete("lock");
//            }
            try{
                stringListMap = cateLogJsonFromDb();
            }finally {
                //lua 脚本删除锁
                String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then   return redis.call(\"del\",KEYS[1]) else   return 0 end";
                Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);//保持删除的原子性
            }

            return stringListMap;
        }else{
            //加锁失败，继续重试
            System.out.println("加锁失败重试....");
            try {
                Thread.sleep(200); //休眠200毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return cataLogJsonFromDbWithRedisLock();//自旋
        }
    }

    /**
     * 数据库获取数据
     *
     * @return
     */
    public Map<String, List<Catelog2Vo>> cateLogJsonFromDb(){
        String category_log = (String) redisTemplate.opsForValue().get("categorys_log");
        if(!StringUtils.isEmpty(category_log)){
            System.out.println("抢占到锁获取的数据");
            return JSON.parseObject(category_log,new TypeReference<Map<String, List<Catelog2Vo>>>(){});//TypeReference 受保护的必须加{} new TypeReference为了转类型
        }
        System.out.println("查询了数据库...");
        List<CategoryEntity> selectLists = baseMapper.selectList(null);
        //查出一级分裂
        List<CategoryEntity> level1Categorys = getParent_cid(selectLists,0L);
        Map<String, List<Catelog2Vo>> collect = level1Categorys.stream().collect(Collectors.toMap(catk1 -> catk1.getCatId().toString(), catv1 -> {
            //查看一级分类对应的二级分类
            List<CategoryEntity> level2Categorys = getParent_cid(selectLists,catv1.getCatId());
            if (level2Categorys != null) {
                List<Catelog2Vo> parent_cid = level2Categorys.stream().map(catv2 -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo(catv1.getCatId().toString(), null, catv2.getCatId().toString(), catv2.getName());
                    if (catv2 != null) {
                        List<CategoryEntity> level3Categorys = getParent_cid(selectLists,catv2.getCatId());
                        List<Catelog2Vo.Catelog3Vo> cl3 = level3Categorys.stream().map(catev3 -> {
                            Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(catv2.getCatId().toString(), catev3.getCatId().toString(), catev3.getName());
                            return catelog3Vo;
                        }).collect(Collectors.toList());
                        catelog2Vo.setCatalog3List(cl3);
                    }

                    return catelog2Vo;
                }).collect(Collectors.toList());

                return parent_cid;
            }

            return null;
        }));
        redisTemplate.opsForValue().set("categorys_log", JSON.toJSONString(collect),1, TimeUnit.DAYS);

        return collect;
    }

    /**
     * 设置本地锁
     *
     * @return
     */
    public Map<String, List<Catelog2Vo>> cataLogJsonFromDbWithLocalLock() {
//        Map<String, List<Catelog2Vo>> category = (Map<String, List<Catelog2Vo>>) cache.get("category");
//        if(category!=null){
//            System.out.println("命中缓存...");
//            return category;
//        }
        /**
         * 1、只要是同一把锁，就能锁住需要这个锁的所有进程
         * synchronized(this) springboot所有组件在容器中都是单例的
         *
         */
        //TODO 本地锁：synchronized (this) JUC(lock)  在分布式锁，想要锁住所有，必须使用分布式锁
        System.out.println("等待锁");
        synchronized (this){
            String category_log = (String) redisTemplate.opsForValue().get("categorys_log");
            if(!StringUtils.isEmpty(category_log)){
                System.out.println("命中缓存..");
                return JSON.parseObject(category_log,new TypeReference<Map<String, List<Catelog2Vo>>>(){});//TypeReference 受保护的必须加{} new TypeReference为了转类型
            }
            System.out.println("获取到锁！");
            List<CategoryEntity> selectLists = baseMapper.selectList(null);
            //查出一级分裂
            List<CategoryEntity> level1Categorys = getParent_cid(selectLists,0L);
            Map<String, List<Catelog2Vo>> collect = level1Categorys.stream().collect(Collectors.toMap(catk1 -> catk1.getCatId().toString(), catv1 -> {
                //查看一级分类对应的二级分类
                List<CategoryEntity> level2Categorys = getParent_cid(selectLists,catv1.getCatId());
                if (level2Categorys != null) {
                    List<Catelog2Vo> parent_cid = level2Categorys.stream().map(catv2 -> {
                        Catelog2Vo catelog2Vo = new Catelog2Vo(catv1.getCatId().toString(), null, catv2.getCatId().toString(), catv2.getName());
                        if (catv2 != null) {
                            List<CategoryEntity> level3Categorys = getParent_cid(selectLists,catv2.getCatId());
                            List<Catelog2Vo.Catelog3Vo> cl3 = level3Categorys.stream().map(catev3 -> {
                                Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(catv2.getCatId().toString(), catev3.getCatId().toString(), catev3.getName());
                                return catelog3Vo;
                            }).collect(Collectors.toList());
                            catelog2Vo.setCatalog3List(cl3);
                        }

                        return catelog2Vo;
                    }).collect(Collectors.toList());

                    return parent_cid;
                }

                return null;
            }));
//        System.out.println("设置map缓存");
//        cache.put("category",collect);
            redisTemplate.opsForValue().set("categorys_log", JSON.toJSONString(collect),1, TimeUnit.DAYS);
            return collect;
        }
    }

    private List<CategoryEntity> getParent_cid(List<CategoryEntity> cates,Long catId) {
        return cates.stream().filter(v->v.getParentCid()==catId).collect(Collectors.toList());
//        return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", catv1.getCatId()));
    }

    /**
     * 递归获取父分类
     *
     *
     * @param catelogId
     * @param longs
     * @return
     */
    private List<Long> getParentById(Long catelogId,List<Long> longs){
        longs.add(catelogId);
        CategoryEntity cate = baseMapper.selectById(catelogId);
        if(cate.getParentCid() != 0 ){
            getParentById(cate.getParentCid(),longs);
        }

        return longs;
    }

    private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> all)
    {
        List<CategoryEntity> collect = all.stream().filter(categoryEntity -> {
            //找到父级菜单的子菜单
            return categoryEntity.getParentCid() == root.getCatId();
        }).map(categoryEntity -> {
            categoryEntity.setChildren(getChildren(categoryEntity, all));
            return categoryEntity;
        }).sorted((menu1,menu2)->{
            return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
        }).collect(Collectors.toList());

        return collect;
    }

}